Skip to content

feat: Add opt-in non-exclusive session locking for session receivers - #60060

Merged
Eldert Grootenboer (EldertGrootenboer) merged 29 commits into
mainfrom
feature/servicebus-nonexclusive-session-37713416
Aug 14, 2026
Merged

Eldert Grootenboer (EldertGrootenboer) merged 29 commits into
mainfrom
feature/servicebus-nonexclusive-session-37713416

Conversation

@EldertGrootenboer

@EldertGrootenboer Eldert Grootenboer (EldertGrootenboer) commented Jun 18, 2026 •

Copy link
Copy Markdown
Member

Adds opt-in non-exclusive session locking to the Service Bus session
receiver, allowing a session that is normally held by a single receiver to be
cooperatively taken over by another receiver. The feature is opt-in and the
default behavior (exclusive session locks) is unchanged, so existing code is
unaffected.

Behavior

Two modes, selected by ServiceBusSessionReceiverOptions.EnableNonExclusiveSession
(default false):

  • Exclusive (default). Unchanged. A session is locked to a single receiver
    for the lock duration, and a second accept on the same session fails as it
    does today.
  • Non-exclusive (opt-in). With EnableNonExclusiveSession set to true,
    the receiver opts into cooperative locking via the AMQP source filter
    com.microsoft:non-exclusive-session-filter, a composite filter carrying the
    session id and, for a takeover, a lock token. The service echoes the filter on
    the attach response with the assigned session lock token, surfaced read-only
    as ServiceBusSessionReceiver.SessionLockToken. A second receiver presents
    that token via ServiceBusSessionReceiverOptions.SessionLockToken to take
    over the session, then settles the original holder's in-flight messages over
    the management link. The displaced holder's receive link is force-detached by
    the service, so its next operation surfaces SessionLockLostException.

If the connected service does not support the feature, opting in fails loudly
with NotSupportedException rather than silently downgrading. Exclusive
sessions never reach the new code paths.

The scope is ServiceBusSessionReceiver only. ServiceBusSessionProcessor has
no equivalent option and continues to lock sessions exclusively; that is
deliberate, is called out in the CHANGELOG entry, and processor support is
tracked internally.

API

public partial class ServiceBusSessionReceiver : ServiceBusReceiver
{
    // The mode the session was actually established under.
    public virtual bool IsSessionExclusive { get; }

    // Token assigned by the service for a non-exclusive session; null otherwise.
    public virtual string SessionLockToken { get; }
}

public partial class ServiceBusSessionReceiverOptions
{
    // Opt into non-exclusive (cooperative) session locking. Default: false.
    public bool EnableNonExclusiveSession { get; set; }

    // Present an existing session lock token to take over a non-exclusive session.
    public System.Guid? SessionLockToken { get; set; }
}

The token is Guid? on input and string on output, matching the existing
message LockToken, which is likewise a service-provided string.

What changed

  • Public API. Four new members across all three TFM API listings: read-only
    IsSessionExclusive and SessionLockToken on the receiver, and settable
    EnableNonExclusiveSession and SessionLockToken on the options. New
    CHANGELOG.md entry.
  • AMQP layer. Source-filter wiring for the composite
    non-exclusive-session-filter, carrying session id then lock token
    (AmqpConnectionScope, AmqpClient, AmqpClientConstants,
    AmqpNonExclusiveSessionFilterCodec). AmqpReceiver relaxes the receive-link
    NotFound settlement guard for non-exclusive sessions so the new holder can
    settle the previous holder's messages over the management link.
  • Receiver layer. Option plumbing and validation in
    ServiceBusSessionReceiver(Options), ServiceBusReceiver(Options), and the
    transport interfaces. New resource strings for the validation and
    NotSupportedException messages.
  • Conventions. cancellationToken moved to the last parameter across the
    internal signatures this touched, and the internal option carriers are now
    get-only with an internal constructor so the shared default-options singleton
    cannot be mutated.

Testing

  • Unit tests cover the opt-in validation paths (a lock token requires
    non-exclusive mode, and requires a specific session id so it cannot be
    combined with accepting the next available session), confirm that a
    non-exclusive accept-next without a session id is allowed, and pin the wire
    contract: the filter descriptor name, its code, and that the session id
    encodes before the lock token, so a swapped field order fails rather than
    round-tripping cleanly.
  • Live integration tests exercise the end-to-end takeover and cross-receiver
    settlement. The five that depend on the service-side feature are [Ignore]d
    with a reason until the rollout reaches the test namespace, so the live
    pipeline stays green; re-enabling them is tracked internally.
  • Full Azure.Messaging.ServiceBus unit suite passes (675 passed, 0 failed).
    dotnet format is clean and the exported API listings regenerate unchanged.

Adds support for non-exclusive session locks, allowing a session to be
cooperatively taken over by another receiver.

- ServiceBusSessionReceiverOptions.IsSessionExclusive (default true) opts into
  non-exclusive locking via the AMQP source filter com.microsoft:session-exclusive-mode.
- The service assigns a com.microsoft:session-lock-token (uuid), surfaced
  read-only as ServiceBusSessionReceiver.SessionLockToken. A second receiver
  presents the token via ServiceBusSessionReceiverOptions.SessionLockToken to
  take over the session and settle the original holder's messages over the
  management link.
- Fails loudly with NotSupportedException against a service that does not
  support the feature; exclusive sessions are unaffected (default path).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in “non-exclusive” session locking mode to Azure.Messaging.ServiceBus session receivers, enabling cooperative session takeovers via AMQP source filters and a service-assigned session lock token, while keeping the default exclusive-lock behavior unchanged.

Changes:

  • Introduces new public APIs: ServiceBusSessionReceiver.SessionLockToken, ServiceBusSessionReceiverOptions.IsSessionExclusive, and ServiceBusSessionReceiverOptions.SessionLockToken, plus a changelog entry and updated API listings.
  • Threads the new session exclusivity/token options through receiver construction into the transport layer, wiring AMQP source filters and surfacing the service-assigned token.
  • Updates AMQP settlement behavior for session receivers to allow cross-receiver settlement paths for non-exclusive sessions; adds unit and gated live tests.

Reviewed changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated no comments.

Show a summary per file
File Description
sdk/servicebus/Azure.Messaging.ServiceBus/tests/Receiver/SessionReceiverTests.cs Adds unit tests for default values and option validation scenarios.
sdk/servicebus/Azure.Messaging.ServiceBus/tests/Receiver/SessionReceiverLiveTests.cs Adds gated live tests covering non-exclusive token assignment and cooperative takeover flows.
sdk/servicebus/Azure.Messaging.ServiceBus/tests/Receiver/ReceiverTests.cs Updates Moq callback signature to match receiver creation signature changes.
sdk/servicebus/Azure.Messaging.ServiceBus/tests/Primitives/ServiceBusConnectionTests.cs Updates test transport client override signature for new receiver parameters.
sdk/servicebus/Azure.Messaging.ServiceBus/tests/Amqp/AmqpConnectionScopeTests.cs Adds unit tests verifying AMQP source-filter construction and token surfacing/backstop behavior.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.resx Adds new resource strings for validation and unsupported-feature errors.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.Designer.cs Regenerates resource accessors for new strings.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Receiver/ServiceBusSessionReceiverOptions.cs Adds opt-in non-exclusive flag and takeover token; plumbs into internal receiver options.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Receiver/ServiceBusSessionReceiver.cs Adds public SessionLockToken and validates option combinations for session acceptance.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Receiver/ServiceBusReceiverOptions.cs Adds internal carriers for session exclusivity and lock token to reach transport layer.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Receiver/ServiceBusReceiver.cs Passes exclusivity/token through to transport receiver creation.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/ServiceBusConnection.cs Extends transport receiver creation to pass exclusivity/token to the transport client.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Core/TransportReceiver.cs Adds abstract SessionLockToken to transport receiver contract.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Core/TransportClient.cs Extends receiver factory method signature to include exclusivity/token.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs Implements token surfacing/backstop and relaxes NotFound settlement guard for non-exclusive sessions.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpConnectionScope.cs Wires AMQP source filters for session-exclusive-mode and session-lock-token.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpClientConstants.cs Adds AMQP symbol constants for new filters/properties.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpClient.cs Threads exclusivity/token into AmqpReceiver construction.
sdk/servicebus/Azure.Messaging.ServiceBus/CHANGELOG.md Documents the new opt-in non-exclusive session locking feature.
sdk/servicebus/Azure.Messaging.ServiceBus/api/Azure.Messaging.ServiceBus.netstandard2.0.cs Updates public API listing for new properties.
sdk/servicebus/Azure.Messaging.ServiceBus/api/Azure.Messaging.ServiceBus.net8.0.cs Updates public API listing for new properties.
sdk/servicebus/Azure.Messaging.ServiceBus/api/Azure.Messaging.ServiceBus.net10.0.cs Updates public API listing for new properties.
Files not reviewed (1)
  • sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.Designer.cs: Generated file

…er params

CreateTransportReceiver gained isSessionExclusive (bool) and sessionLockToken (Guid?) trailing params. GetMockConnection's Moq .Callback still used the 9-arg signature, which compiles but throws ArgumentException at runtime (Moq validates callback arity). Expand the callback to 11 type args + lambda params, mirroring ReceiverTests.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 23 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.Designer.cs: Generated file

Vinay Suryanarayana and others added 3 commits June 29, 2026 15:55
The broker supports acquiring the next available session non-exclusively
(sessionId=null, non-exclusive). Remove the incorrect client-side validation
that rejected AcceptNextSessionAsync with IsSessionExclusive=false, and drop
the now-unused resource string and its unit test. A takeover lock token still
requires a specific session.
…sions

- Add AmqpNonExclusiveSessionFilterCodec encode/decode round-trip tests (session id + token, no token, accept-any null session id)

- Add AcceptNextSessionAllowsNonExclusiveWithoutSessionId verifying accept-any is allowed in non-exclusive mode

@vinaysurya vinaysurya left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕐

Addresses review feedback: the codec carries meaningful fields (session id and lock token), so provide a ToString() that describes them for diagnostics. Adds a unit test covering the output.
Addresses review feedback: covers AcceptNextSessionAsync (the accept-any path with no session id) plus IsSessionExclusive=false, then a token-based takeover and cross-receiver settle. Gated with the existing NonExclusiveFeatureSkipReason like its siblings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.Designer.cs: Generated file

Comment thread sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.resx
Addresses Copilot review feedback: the filter-construction tests use a single composite non-exclusive session filter (session id + lock token), not separate mode/token filters; update the doc comments to match. Reword the NotSupportedException message in terms of feature availability for the namespace rather than a namespace 'version'.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.Designer.cs: Generated file

Comment thread sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpConnectionScope.cs Outdated
Addresses Copilot review feedback: the comment referenced only the plain session filter, but a non-exclusive session receiver adds the composite non-exclusive session filter instead. Reword to cover both branches.
This was referenced Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants